package cn.cdeden.job.snailjob.snailjob.service;

import cn.cdeden.job.snailjob.snailjob.domain.AdDepartment;
import cn.cdeden.job.snailjob.snailjob.domain.enums.AdRegionConstant;
import cn.cdeden.system.api.RemoteDeptService;
import cn.cdeden.system.api.domain.vo.RemoteDeptVo;
import cn.cdeden.system.api.domain.vo.RemoteUserVo;
import lombok.RequiredArgsConstructor;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;
import java.util.List;
import java.util.Map;

@Component
@RequiredArgsConstructor
public class SyncDepartmentService {

    private final LdapSevice ldapSevice;
    private final SyncUserService userService;

    private static final String AD_DOMAIN_DOMAINNAME = "ad_domain_domainName";
    private static final String AD_DOMAIN_OU = "ad_domain_ou";

    @DubboReference
    private RemoteDeptService deptService;

    /**
     * 同步部门以及人员
     * @param ctx
     * @param config
     */
    public void syncDepartmentsAndUsers(LdapContext ctx, Map<String, String> config) throws NamingException {

        //LDAP搜索过滤器类，此处只获取AD域用户，所以条件为用户user或者person均可
        String domainName = config.get(AD_DOMAIN_DOMAINNAME);
        String[] split = domainName.split("\\.");
        String dc = "";
        for (int i = 0; i < split.length; i++) {
            dc = dc + ",DC=" + split[i];
        }
        //AD域节点结构
        String searchBase = "OU=" + config.get(AD_DOMAIN_OU) + dc;

        AdDepartment treeAdDepartment = new AdDepartment();
        NamingEnumeration<SearchResult> answer = getDeptSearchResultRoot(ctx, AdRegionConstant.AD_DEPT_FILTER, searchBase);
        while (answer.hasMoreElements()) {
            SearchResult sr = answer.next();
            String name = ldapSevice.getAttrValue(sr, "name");
            String canonicalName = ldapSevice.getAttrValue(sr, "canonicalName");
            String distinguishedName = ldapSevice.getAttrValue(sr, "distinguishedName");


            treeAdDepartment.setId(ldapSevice.getAttrValue(sr, "objectGUID"));
            treeAdDepartment.setName(name);
            treeAdDepartment.setcName(canonicalName);
            treeAdDepartment.setDistinguishedName(distinguishedName);
        }

        fetchNextLevelOu(ctx, searchBase, treeAdDepartment);

        // 遍历部门树，同时同步部门和人员
        syncDepartmentAndUsers(ctx, treeAdDepartment, null, config);

    }

    /**
     * 在LDAP目录中搜索部门相关的搜索结果
     *
     * @param ctx         LDAP上下文对象，用于执行搜索操作
     * @param searchFilter 搜索过滤器，用于定义搜索条件
     * @param searchBase  搜索基准，指定搜索的起始点
     * @return NamingEnumeration<SearchResult> 搜索结果的枚举，包含匹配的条目
     * @throws NamingException 如果在命名操作中发生错误，抛出此异常
     */
    public NamingEnumeration<SearchResult> getDeptSearchResultRoot(LdapContext ctx, String searchFilter, String searchBase) throws
        NamingException {
        //搜索控制器
        SearchControls searchCtls = new SearchControls();
        //创建搜索控制器
        searchCtls.setSearchScope(SearchControls.OBJECT_SCOPE);
        String returnedAtts[] = {"canonicalName", "distinguishedName", "objectGUID",
            "name", "userPrincipalName", "departmentNumber", "telephoneNumber", "homePhone",
            "mobile", "department", "sAMAccountName", "whenChanged", "primaryGroupID", "mail"}; // 定制返回属性
        searchCtls.setReturningAttributes(returnedAtts);
        NamingEnumeration<SearchResult> answer = ctx.search(searchBase, searchFilter, searchCtls);
        return answer;
    }

    /**
     * 递归获取下一级组织单位（OU）并构建组织结构树
     *
     * @param ctx            LDAP上下文对象
     * @param searchBase     搜索基准，用于定位组织单位
     * @param treeAdDepartment 当前处理的根部门对象
     * @throws NamingException 如果在命名操作中发生错误，抛出此异常
     */
    private void fetchNextLevelOu(LdapContext ctx, String searchBase, AdDepartment treeAdDepartment) throws NamingException {
        // 获取搜索结果
        NamingEnumeration<SearchResult> answer = getDeptSearchResult(ctx, AdRegionConstant.AD_DEPT_FILTER, searchBase);

        while (answer.hasMoreElements()) {
            SearchResult sr = answer.next();
            String name = ldapSevice.getAttrValue(sr, "name");
            String canonicalName = ldapSevice.getAttrValue(sr, "canonicalName");
            String distinguishedName = ldapSevice.getAttrValue(sr, "distinguishedName");

            // 创建子部门对象
            AdDepartment childDepartment = new AdDepartment();
            childDepartment.setId(ldapSevice.getAttrValue(sr, "objectGUID"));
            childDepartment.setName(name);
            childDepartment.setcName(canonicalName);
            childDepartment.setDistinguishedName(distinguishedName);

            // 递归获取子OU的组织结构，并将其添加到当前部门对象的子部门列表中
            fetchNextLevelOu(ctx, distinguishedName, childDepartment);

            // 将子部门添加到根部门对象的子部门列表中
            treeAdDepartment.addChildren(childDepartment);
        }
    }

    /**
     * 在LDAP目录中搜索部门相关的搜索结果
     *
     * @param ctx         LDAP上下文对象，用于执行搜索操作
     * @param searchFilter 搜索过滤器，用于定义搜索条件
     * @param searchBase  搜索基准，指定搜索的起始点
     * @return NamingEnumeration<SearchResult> 搜索结果的枚举，包含匹配的条目
     * @throws NamingException 如果在命名操作中发生错误，抛出此异常
     */
    public NamingEnumeration<SearchResult> getDeptSearchResult(LdapContext ctx, String searchFilter, String searchBase) throws
        NamingException {
        //搜索控制器
        SearchControls searchCtls = new SearchControls();
        //创建搜索控制器
        searchCtls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
        String returnedAtts[] = {"canonicalName", "distinguishedName", "objectGUID",
            "name", "userPrincipalName", "departmentNumber", "telephoneNumber", "homePhone",
            "mobile", "department", "sAMAccountName", "whenChanged", "primaryGroupID", "mail"}; // 定制返回属性
        searchCtls.setReturningAttributes(returnedAtts);
        NamingEnumeration<SearchResult> answer = ctx.search(searchBase, searchFilter, searchCtls);
        return answer;
    }

    /**
     * 同步部门及其下属人员信息
     *
     * @param ctx         LDAP上下文对象，用于执行LDAP操作
     * @param department  待同步的部门对象
     * @param parentId    父部门ID
     * @param config      配置信息，包含LDAP相关配置
     * @throws NamingException 如果在LDAP命名操作中发生错误，抛出此异常
     */
    public void syncDepartmentAndUsers(LdapContext ctx, AdDepartment department, Long parentId, Map<String, String> config) throws NamingException {
        // 同步部门信息
        RemoteDeptVo dept = syncDepartment(department, parentId);

        // 如果部门路径包含“在职”，直接返回，不同步“在职”作为部门
        if ("在职".equals(department.getName())) {
            // 同步部门下的人员信息
            List<RemoteUserVo> adUsers = userService.getAdUsers(ctx, config, dept.getPath());
            for (RemoteUserVo user : adUsers) {
                userService.syncUser(ctx, user, parentId);
            }
        }else{
            // 同步部门下的人员信息
            List<RemoteUserVo> adUsers = userService.getAdUsers(ctx, config, dept.getPath());
            for (RemoteUserVo user : adUsers) {
                userService.syncUser(ctx, user, dept.getDeptId());
            }
        }

        // 递归同步子部门
        for (AdDepartment childDepartment : department.getChildren()) {
            syncDepartmentAndUsers(ctx, childDepartment, dept.getDeptId(), config);
        }
    }

    /**
     * 同步部门信息到远程服务
     *
     * @param department 待同步的部门对象
     * @param parentId   父部门ID
     * @return 同步后的远程部门对象
     */
    public RemoteDeptVo syncDepartment(AdDepartment department, Long parentId) {
        RemoteDeptVo dept = new RemoteDeptVo();
        String cName = department.getcName();
        dept.setDeptName(department.getName());
        dept.setParentId(parentId);
        dept.setStatus("0");

        List<RemoteDeptVo> byPath = deptService.getByPath(cName);

        if (!(byPath.size() > 0)) {

            //如果是在职的情况就不增加这层级，直接返回上一级
            if("在职".equals(department.getName())){
                return dept;
            }

            deptService.saveReturnId(dept);
        } else {
            // 如果部门已存在，则更新部门信息
            // 注意：此处需要根据实际情况来确定是否需要更新部门信息
            // 根据部门路径获取已存在的部门信息
            RemoteDeptVo existingDept = deptService.getByPath(dept.getPath()).get(0);
            return existingDept;
        }
        return dept;
    }


}
